/** filesystem.d - 

  Written in the D programming language 1.0
  This file is part of the Dotmars Framework

Authors:	Wei Li <oldrev@gmail.com>
Copyright:	Copyright (c) 2007 by Wei Li
License:	BSD
 */

module dotmars.io.filesystem;


import dotmars.base.stdexcept;
import dotmars.base.string;
import dotmars.base.time;
import dotmars.io.ioexcept;
import dotmars.io.filestream;
import dotmars.io.path;


version(Win32){

	import dotmars.platform.windows.core;
	import dotmars.platform.windows.util;
	import dotmars.text.utf;

	const DWORD MaxPath = MAX_PATH;

	public enum FileAttributes : DWORD
	{
		Archive = FILE_ATTRIBUTE_ARCHIVE,
				Compressed = FILE_ATTRIBUTE_COMPRESSED,
				Directory = FILE_ATTRIBUTE_DIRECTORY,
				Encrypted = FILE_ATTRIBUTE_ENCRYPTED,
				Hidden = FILE_ATTRIBUTE_HIDDEN,
				Normal = FILE_ATTRIBUTE_NORMAL,
				Offline = FILE_ATTRIBUTE_OFFLINE,
				ReadOnly = FILE_ATTRIBUTE_READONLY,
				ReparsePoint = FILE_ATTRIBUTE_REPARSE_POINT,
				SparseFile = FILE_ATTRIBUTE_SPARSE_FILE,
				System = FILE_ATTRIBUTE_SYSTEM,
				Temporary = FILE_ATTRIBUTE_TEMPORARY,
				Device = FILE_ATTRIBUTE_DEVICE,
				NotContentIndexed = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
	}

	private void w32GetFileInfo(string path, WIN32_FIND_DATAW* fd)
	{
			HANDLE hfind = FindFirstFileW(path.toUtf16z(), fd);

			scope(exit) {
				CloseHandle(hfind);
			}

			if(hfind == INVALID_HANDLE_VALUE)
				throw new FileNotFoundException("Failed to find file");
	}


}//version(Win32)
else version(Posix){

	import dotmars.runtime.stdclib.stdio;
	import dotmars.platform.posix.sys.stat;
	import dotmars.platform.posix.fcntl;
	import dotmars.platform.posix.unistd;

	const size_t MaxPath = 256;

	public enum FileAttributes : uint
	{
		Hidden = 0x10,
			   Directory = 0x4,
			   ReadOnly = 0x80,
			   Device = 0x1000,
			   ////////////////////////////
			   //Posix好像不支持
			   Archive = 0x1,
			   Compressed = 0x2,
			   Encrypted = 0x8,
			   Normal = 0x20,
			   Offline = 0x40,
			   ReparsePoint = 0x100,
			   SparseFile = 0x200,
			   System = 0x400,
			   Temporary = 0x800,
			   NotContentIndexed = 0x2000
	}

}//version(Posix)


static final class File
{

	//////////////////// 平台无关代码 ////////////////////////////

	static void[] read(string filename)
	in {
		assert(filename !is null);
	}
	body {
		auto fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
		void[] content;
		content.length = cast(size_t)fs.length;
		uint len = fs.read(content);
		if(cast(long)len != fs.length)
			throw new IOException("Failed to read entire file: " ~ filename);
		scope(exit) fs.close();
		return content;
	}

	static void write(string filename, void[] content)
	in {
		assert(content !is null);
		assert(content.length > 0);
	}
	body
	{
		auto fs = new FileStream(filename, FileMode.Truncate, FileAccess.Write, FileShare.None);
		fs.write(content);
		scope(exit) fs.close();
	}


	static FileStream create(string filename)
	in {
		assert(filename !is null);
	}
	body {
		auto fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
		return fs;
	}

	static FileStream open(string filename)
	in {
		assert(filename !is null);
	}
	body {
		auto fs = new FileStream(filename, FileMode.Open, FileAccess.ReadWrite);
		return fs;
	}

	static void copy(string from, string to)
	in {
		assert(from !is null);
		assert(to !is null);
	}
	body
	{
		copy(from, to, false);
	}

	////////////////////////////////////////////////////////////

	version(Windows){

		static void copy(string from, string to, bool overwrite)
		in
		{
			assert(from !is null);
			assert(to !is null);
			assert(from.length > 0);
			assert(to.length > 0);
		}
		body
		{
			BOOL failIfExists  = overwrite ? FALSE : TRUE;
			BOOL r = CopyFileW(from.toUtf16z(), to.toUtf16z(), failIfExists);
			if(r == FALSE)
				throw new IOException("Failed to copy file");
		}

		static bool exists(string filename)
		{
			if(filename is null)return false;
			DWORD attr = GetFileAttributesW(filename.toUtf16z());
			if(attr == INVALID_FILE_ATTRIBUTES) return false;
			if(attr & FILE_ATTRIBUTE_DIRECTORY) return false;
			if(attr & FILE_ATTRIBUTE_DEVICE) return false;
			return true;
		}

		static void remove(string filename)
		in {
			assert(filename !is null);
			assert(filename.length > 0);
		}
		body
		{
			BOOL r = DeleteFileW(filename.toUtf16z());
			if(r == FALSE)
				throw new IOException("Failed to delete file");
		}

		static void move(string from, string to)
		in {
			assert(from !is null);
			assert(to !is null);
			assert(from.length > 0);
			assert(to.length > 0);
		}
		body {
			if(!exists(from))
				throw new IOException("The source file was not existed");
			if(exists(to))
				throw new IOException("The dest file was already existed");
			BOOL r = MoveFileW(from.toUtf16z(), to.toUtf16z());
			if(r == FALSE)
				throw new IOException("Failed to move file");
		}

		static long getLength(string filename)
		in {
			assert(filename !is null);
			assert(filename.length > 0);
		}
		body {
			WIN32_FIND_DATAW fd = void;
			w32GetFileInfo(filename, &fd);
			return (cast(long)(fd.nFileSizeHigh) << DWORD.sizeof) + fd.nFileSizeLow;
		}


		static FileAttributes getAttributes(string filename)
		in {
			assert(filename !is null);
			assert(filename.length > 0);
		}
		body {
			DWORD dwattr = GetFileAttributesW(filename.toUtf16z());
			if(dwattr == INVALID_FILE_ATTRIBUTES)
				throw new IOException("Failed to get attribute of " ~ filename);

			FileAttributes attrs = cast(FileAttributes) dwattr;
			return attrs;
		}

		static void setAttributes(string filename, FileAttributes attributes)
		in {
			assert(filename !is null);
			assert(filename.length > 0);
		}
		body {
			DWORD dwattr = cast(DWORD) attributes;
			BOOL r = SetFileAttributesW(filename.toUtf16z(), dwattr);
			if(r == FALSE)
				throw new IOException("Failed to set file attributes of " ~ filename); 
		}

		static DateTime getLastAccessTimeUtc(string path)
		in {
			assert(path !is null);
		}
		body
		{
			WIN32_FIND_DATAW fd = void;
			w32GetFileInfo(path, &fd);

			DateTime ret = DateTime.fromWindowsFileTimeUtc(fileTimeToLong(fd.ftLastAccessTime));
			return ret;
		}

		static DateTime getLastWriteTimeUtc(string path)
		in {
			assert(path !is null);
		}
		body {
			WIN32_FIND_DATAW fd = void;
			w32GetFileInfo(path, &fd);

			DateTime ret = DateTime.fromWindowsFileTimeUtc(fileTimeToLong(fd.ftLastWriteTime));
			return ret;
		}


	}//version(Windows)

	version(Posix){

		static void copy(string from, string to, bool overwrite)
		in {
			assert(from !is null);
			assert(to !is null);
			assert(from.length > 0);
			assert(to.length > 0);
		}
		body {
			//??
		}

		static bool exists(string filename)
		in {
			assert(filename !is null);
		}
		body {
			try 
			{
				FileAttributes attr = getAttributes(filename);
				if(attr & FileAttributes.Directory)
					return false;
			}
			catch(IOException) 
			{
				return false;
			}
			return true;
		}

		static void remove(string filename)
		in {
			assert(filename !is null);
			assert(filename.length > 0);
			assert(filename.length <= MaxPath);
		}
		body {
			unlink(toStringz(filename));
		}

		static void move(string from, string to)
			in {
				assert(from !is null);
				assert(to !is null);
				assert(from.length > 0);
				assert(to.length > 0);
				assert(from.length <= MaxPath);
				assert(to.length <= MaxPath);
			}
		body {
			if(!exists(from))
				throw new IOException("The source file was not existed");
			if(exists(to))
				throw new IOException("The dest file was already existed");

			int r = rename(from.toStringz(), to.toStringz());

			if(r != 0)
				throw new IOException("Failed to move file");
		}

		static long getLength(string filename)
		in {
			assert(filename !is null);
			assert(filename.length > 0 && filename.length <= MaxPath);
		}
		body {
			//TODO: leaked 64-bits filesystem support
			stat_t statbuf = void;
			int r = stat(filename.toStringz(), &statbuf);
			if(r != 0)
				throw new IOException("Failed to get file length");
			return statbuf.st_size;
		}


		static FileAttributes getAttributes(string filename)
		in {
			assert(filename !is null);
		}
		body {
			stat_t statbuf = void;
			int r = stat(filename.toStringz(), &statbuf);
			if(r)
				throw new IOException("Failed to get file status");
			FileAttributes attrs = cast(FileAttributes)0;
			if(statbuf.st_mode & O_RDONLY)
				attrs |= FileAttributes.ReadOnly;
			if(statbuf.st_mode & S_IFDIR)
				attrs |= FileAttributes.Directory;
			return attrs;
		}

		static void setAttributes(string filename, FileAttributes attributes)
		in {
			assert(filename !is null);
		}
		body {
			//Do nothing
		}

		//damn, Posix does not supports this
		//static DateTime getCreationTimeUtc(string path)

		static DateTime getLastAccessTimeUtc(string path)
		in {
			assert(path !is null);
		}
		body {
			stat_t statbuf = void;
			int r = stat(path.toStringz(), &statbuf);
			if(r != 0)
				throw new IOException("Failed to get file last access time");
			return DateTime.fromPosixTimeUtc(statbuf.st_atime);
		}

		static DateTime getLastWriteTimeUtc(string path)
		in {
			assert(path !is null);
		}
		body {
			stat_t statbuf = void;
			int r = stat(path.toStringz(), &statbuf);
			if(r != 0)
				throw new IOException("Failed to get file last access time");
			return DateTime.fromPosixTimeUtc(statbuf.st_mtime);
		}


	}//version(Posix)

} //class File




static final class Directory
{

	//Common code:

	static bool exists(string path)
	in {
		assert(path !is null);
	}
	body 
	{
		try 
		{
			FileAttributes attr = File.getAttributes(path);
			if(attr & FileAttributes.Directory) return true;
		}
		catch(IOException) 
		{
			return false;
		}
		return false;
	}

	///////////////////////////////////////////////////////

	version(Windows)
	{
		public static string getCurrentDirectory()
		{
			wchar[MaxPath] curdir;
			uint len = GetCurrentDirectoryW(MaxPath, curdir.ptr);
			if(len == 0)
				throw new IOException("Failed to get current directory");
			return .toUtf8(curdir[0..len]);
		}

		public static void setCurrentDirectory(string path)
		in {
			assert(path !is null);
		}
		body {
			BOOL r = SetCurrentDirectoryW(path.toUtf16z());
			if(r == FALSE)
				throw new IOException("Failed to set current directory as " ~ path);
		}

		static void move(string from, string to)
		in
		{
			assert(from !is null);
			assert(to !is null);
			assert(from.length > 0);
			assert(to.length > 0);
		}
		body
		{
			if(!exists(from))
				throw new IOException("The source directory was not existed");
			if(exists(to))
				throw new IOException("The dest directory was already existed");
			BOOL r = MoveFileW(from.toUtf16z(), to.toUtf16z());
			if(r == FALSE)
				throw new IOException("Failed to move directory");
		}

	}//version(Windows)
	else version(Posix)
	{

		public static string getCurrentDirectory()
		{
			char[MaxPath] curdir;
			char* r = getcwd(curdir.ptr, curdir.length);

			if(r is null)
				throw new IOException("Failed to get current directory");

			return .toString(curdir.ptr);
		}

		public static void setCurrentDirectory(string path)
		in {
			assert(path !is null);
		}
		body {
			if(chdir(toStringz(path)) == -1)
				throw new IOException("Failed to set current directory as " ~ path);
		}

		public static DateTime getLastAccessTimeUtc(string path)
		{
			return File.getLastAccessTimeUtc(path);
		}

		public static DateTime getLastWriteTimeUtc(string path)
		{
			return File.getLastWriteTimeUtc(path);
		}


	}//version(Posix)

} //class Directory



// Notes:
// We never need the FileInfo & DirectoryInfo classes, 
// because we have not the security checking like .Net FX.
